package org.fujay.langchain4jserver.domain.knowledge.aggregate;

import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.StrUtil;
import lombok.*;
import org.fujay.langchain4jserver.domain.knowledge.entity.File;
import org.fujay.langchain4jserver.domain.knowledge.entity.KnowledgeBase;
import org.fujay.langchain4jserver.domain.knowledge.vo.*;
import org.fujay.langchain4jserver.domain.model.vo.ModelId;
import org.fujay.langchain4jserver.domain.shared.exception.DomainException;

import java.time.LocalDateTime;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 知识库聚合根
 */
@Getter
@Setter(AccessLevel.PACKAGE)
@AllArgsConstructor(access = AccessLevel.PACKAGE)
public class KnowledgeBaseAggregate {

    @NonNull
    private final KnowledgeBase knowledgeBase;

    private final Map<FileId, File> files;

    /**
     * 创建知识库，不包含文件，避免聚合根过大
     */
    public static KnowledgeBaseAggregate create(
            KnowledgeBaseId id,
            String name,
            String description,
            String indexName,
            ModelId embeddingModelId,
            String namespace) {
        KnowledgeBase kb =
                KnowledgeBase.create(id, name, description, indexName, embeddingModelId, namespace);
        return new KnowledgeBaseAggregate(kb, new HashMap<>());
    }

    // 加载已存在的知识库聚合（供仓储使用）
    public static KnowledgeBaseAggregate load(KnowledgeBase knowledgeBase, Map<FileId, File> files) {
        return new KnowledgeBaseAggregate(knowledgeBase, new HashMap<>(files));
    }

    // --- 聚合根行为 ---

    public void updateInfo(String name, String description, ModelId embeddingModelId) {
        // 检查模型是否变更
        boolean modelChanged = !knowledgeBase.getEmbeddingModelId().equals(embeddingModelId);
        knowledgeBase.updateInfo(name, description, embeddingModelId);
        if (modelChanged && !files.isEmpty()) {
            // 如果模型改变且已有文件，标记需要重新索引
            knowledgeBase.setStatus(KnowledgeBaseStatus.NEEDS_REINDEX);
            // 同时需要将所有文件状态标记为 PENDING
            files.values().forEach(File::markAsPending);
        }
    }

    // 添加文件到知识库
    public void addFile(File file) {
        if (files.containsKey(file.getId())) {
            return;
        }
        files.put(file.getId(), file);
        knowledgeBase.setDocumentCount(knowledgeBase.getDocumentCount().increment());
        // 添加文件后，如果知识库是空闲状态，则变为部分索引中
        if (knowledgeBase.getStatus().isIdle()) {
            knowledgeBase.setStatus(KnowledgeBaseStatus.INDEXING_PARTIAL);
        }
    }

    // 删除文件
    public void removeFile(FileId fileId) {
        File removedFile = files.remove(fileId);
        if (removedFile != null) {
            knowledgeBase.setDocumentCount(knowledgeBase.getDocumentCount().decrement());
            updateKnowledgeBaseStatusBasedOnFiles();
        }
    }

    // 更新文件索引状态（由索引服务回调）
    public void updateFileIndexStatus(
            FileId fileId, List<String> documentIds, IndexStatus status, String errorMessage) {
        File file = files.get(fileId);
        if (file != null) {
            file.updateIndexStatus(status, StrUtil.join(StrPool.COMMA, documentIds), errorMessage);
            // 文件状态更新后，重新计算知识库聚合状态
            updateKnowledgeBaseStatusBasedOnFiles();
        }
    }

    // 触发全量索引
    public void triggerFullIndexing() {
        if (files.isEmpty()) {
            knowledgeBase.setStatus(KnowledgeBaseStatus.EMPTY);
            return;
        }
        if (knowledgeBase.getStatus().isIndexing()) {
            throw new DomainException("Knowledge base is already indexing.");
        }
        knowledgeBase.setStatus(KnowledgeBaseStatus.INDEXING_FULL);
        // 将所有需要索引的文件标记为 PENDING
        files.values().stream()
                .filter(
                        f ->
                                f.getIndexStatus() == IndexStatus.FAILED
                                        || f.getIndexStatus() == IndexStatus.PENDING
                                        || knowledgeBase.getStatus() == KnowledgeBaseStatus.NEEDS_REINDEX)
                .forEach(File::markAsPending);
        // TODO: 发布 FullIndexTriggered 事件
    }

    // 触发单个文件重新索引
    public void triggerFileReindexing(FileId fileId) {
        File file = files.get(fileId);
        if (file == null) {
            throw new DomainException("File not found in knowledge base.");
        }
        if (file.getIndexStatus() != IndexStatus.FAILED
                && file.getIndexStatus() != IndexStatus.COMPLETED) {
            throw new DomainException(
                    "File must be in FAILED or COMPLETED status to trigger re-indexing.");
        }
        file.markAsPending();
        if (knowledgeBase.getStatus().isIdle()) {
            knowledgeBase.setStatus(KnowledgeBaseStatus.INDEXING_PARTIAL);
        }
        // TODO: 发布 FileReindexTriggered 事件
    }

    // 触发知识库重建
    public void triggerRebuild() {
        if (knowledgeBase.getStatus().isIndexing()) {
            throw new DomainException("Cannot rebuild while indexing is in progress.");
        }
        knowledgeBase.setStatus(KnowledgeBaseStatus.INDEXING_FULL); // 开始全量索引
        files.values().forEach(File::markAsPending); // 所有文件标记为待处理
        // TODO: 发布 KnowledgeBaseRebuildTriggered 事件 (包含清除向量库的指令)
    }

    // 根据文件状态更新知识库聚合状态
    private void updateKnowledgeBaseStatusBasedOnFiles() {
        if (files.isEmpty()) {
            knowledgeBase.setStatus(KnowledgeBaseStatus.EMPTY);
            knowledgeBase.setDocumentCount(DocumentCount.zero());
            return;
        }

        boolean anyFailed = false;
        boolean anyProcessing = false;
        boolean allCompleted = true;

        for (File file : files.values()) {
            IndexStatus fs = file.getIndexStatus();
            if (fs == IndexStatus.FAILED) {
                anyFailed = true;
                allCompleted = false;
                break;
            } else if (fs != IndexStatus.COMPLETED) {
                allCompleted = false; // 只要有一个未完成，整体就不是完成
                if (fs == IndexStatus.PENDING
                        || fs == IndexStatus.PARSING
                        || fs == IndexStatus.VECTORIZING
                        || fs == IndexStatus.STORING) {
                    anyProcessing = true;
                }
            }
        }

        KnowledgeBaseStatus currentKbStatus = knowledgeBase.getStatus();
        KnowledgeBaseStatus newKbStatus;

        if (anyFailed) {
            newKbStatus = KnowledgeBaseStatus.INDEXING_FAILED;
        } else if (allCompleted) {
            newKbStatus = KnowledgeBaseStatus.INDEXING_COMPLETED;
        } else if (anyProcessing || currentKbStatus == KnowledgeBaseStatus.INDEXING_FULL) {
            // 如果有文件在处理中，或者当前是全量索引状态，则聚合状态为索引中
            // 如果之前是全量，保持全量；否则是部分
            newKbStatus =
                    (currentKbStatus == KnowledgeBaseStatus.INDEXING_FULL)
                            ? KnowledgeBaseStatus.INDEXING_FULL
                            : KnowledgeBaseStatus.INDEXING_PARTIAL;
        } else {
            // 没有失败，没有处理中，也不是全部完成，可能是NEEDS_REINDEX或刚添加文件还没开始处理的状态
            newKbStatus =
                    (currentKbStatus == KnowledgeBaseStatus.NEEDS_REINDEX)
                            ? KnowledgeBaseStatus.NEEDS_REINDEX
                            : KnowledgeBaseStatus.INDEXING_PARTIAL;
        }

        knowledgeBase.setStatus(newKbStatus);
    }

    // 提供对内部实体的只读访问
    public KnowledgeBaseId getId() {
        return knowledgeBase.getId();
    }

    public String getName() {
        return knowledgeBase.getName();
    }

    public String getDescription() {
        return knowledgeBase.getDescription();
    }

    public ModelId getEmbeddingModelId() {
        return knowledgeBase.getEmbeddingModelId();
    }

    public KnowledgeBaseStatus getStatus() {
        return knowledgeBase.getStatus();
    }

    public String getIndexName() {
        return knowledgeBase.getIndexName();
    }

    public DocumentCount getDocumentCount() {
        return knowledgeBase.getDocumentCount();
    }

    public Map<FileId, File> getFiles() {
        return Collections.unmodifiableMap(files);
    }

    // --- Delegated Getters for Read-Only Access ---

    public LocalDateTime getCreatedAt() {
        return knowledgeBase.getCreateTime();
    }

    public LocalDateTime getUpdatedAt() {
        return knowledgeBase.getUpdateTime();
    }

    /**
     * Returns an unmodifiable list of files associated with this knowledge base.
     *
     * @return An unmodifiable list of files.
     */
    public List<File> listFiles() {
        return files != null ? files.values().stream().toList() : Collections.emptyList();
    }
}
